/**
 * 通用单值检测
 * @param typ string|RegExp|function|array 检测规则
 * 		array: 多条件取或  首值true:可空  每项:string|RegExp|function
 * 		function: 回调检测  执行及返回: !!typ(val:string)
 * 		RegExp: 正则表达式检测  返回: !!typ.test(val)
 * 		string: 验证规则
 * 			- 可空: 规则前加'?', 空值仅限:null,空字符,undefined
 * 			- 区间: 数学,省略值不限 可小数,可大数,无e格式(无检测)
 * 				特别的: 前加'!'\ -> 表示检测区间外为检测通过
 * 			- 长度: 取字符长度(如单个汉字长度取1,同MYSQL版本>=5.x)
 * 			# 预定义KEY: 使用$._is预定义或配置规则检测
 * 			# {STR}:{ter}  字符串验证 {STR}为$._is_str定义之一, {ter}为字符串长度区间
 * 			# int:{ter}  整数 {ter}限制区间 无前缀:大小限制,前缀限制可用(非0前0移除):
 * 				'+':>=0且位数限制,  '-':<0且位数限制(不含-),  '*':仅位数限制(不含-)
 * 			# num:{t1}#{t2}  数字 {t1}大小或整数区间, {t2}为小数位数区间(含末尾的0)
 * 				{t1}: 无前缀:大小限制, 前缀:正负及整数位数限制,可用(非0前0移除):
 * 					'+': >=0且位数限制, '-': <0且位数限制(不含-), '*':仅位数限制(不含-)
 * 			# len:{ter}  字符串长度(字符不限)  {ter}为字符串长度区间
 * 			# enum:{list}  枚举  {list}:可选值,','分割,前后无逗号
 * 			# time:{ter}  时间戳(秒)限制  {ter}:必需,相对当前时间差(秒)区间(val-now)
 * @param val mixed 要检测的值
 * @return boolean  检测是否通过
 */
$.is = function(typ, val){
	const TIME = function(val, ter){
		if(!REG(val,$._is.int) || val<=0) return false;
		return TER(val-Math.ceil(new Date()*1/1000),ter);
	};
	const ENUM = function(val, ext){
		ext=','+ext+',';  val=','+val+',';
		return ext.indexOf(val)>=0;
	};
	const LEN = function(val, ter){ return TER(val.length,ter); };
	const NUM = function(val, typ){
		if(!REG(val,$._is.num)) return false;  let ts=typ.split('#');
		if((val+='')[0]==='+') val=val.slice(1); let vs=val.split('.');
		if(ts[0]){
			let T=ts[0], p=T[0], x=p==='+'||p==='-'||p==='*';
			if(!(x?INT(vs[0],T):TER(val,T))) return false;
		}
		return !ts[1] || TER(vs[1]?.length||0, ts[1]);
	};
	const INT = function(val, ter){
		if(!REG(val,$._is.int)) return false;  let x=ter[0];
		if(x!=='+' && x!=='-' && x!=='*') return TER(val,ter);
		const _ = val[0]==='-'; // 是否负数
		if((x==='+' && _) || (x==='-' && !_)) return false;
		if(_) val=val.slice(1);  val=val.replace(/^0+/,'');
		return TER(val.length||1, ter.slice(1));
	};
	const TER = function(val, ter){
		let m=/^(!?)([([])(-?\d+\.?\d*)?,(-?\d+\.?\d*)?([)\]])$/.exec(ter);
		if(!m) throw new Error('$.is ter: '+ter);
		let cl=m[2]==='[', min=m[3]?$.bcCom(val,m[3]):9;
		let cr=m[5]===']', max=m[4]?$.bcCom(val,m[4]):9;
		if(m[1]){
			if(min!==9 && (cl?min<0:min<=0)) return true;
			return max===9 || (cr?max>0:max>=0);
		}
		return (min===9 || (cl?min>=0:min>0))
			&& (max===9 || (cr?max<=0:max<0));
	};
	const STR = function(val, typ, ter){
		if(!REG(val,$._is[typ])) return false;
		return !ter || TER(val.length,ter);
	};
	const REG = function(val, reg){ return !!reg.test(val); };
	const ONE = function(m, v){
		if(typeof m==='function') return !!m(v);  m=$._is[m]||m;
		if(m instanceof RegExp) return REG(v,m); let [t,...s]=m.split(':');
		s=s.join(':'); if($._is_str.indexOf(`,${t},`)>0) return STR(v,t,s);
		if(t==='int') return INT(v,s);  if(t==='num') return NUM(v,s);
		if(t==='len') return LEN(v,s);  if(t==='enum') return ENUM(v,s);
		if(t==='time') return TIME(v,s); throw new Error('$.is typ: '+m);
	};
	$.is = function(typ, val){
		if(typeof val==='number') val+=''; else if($.isNil(val,true)) val='';
		else if(typeof val!=='string') return false;  let isNul=val==='';
		if(typeof typ==='function') return !!typ(val);
		if(typeof typ==='string'){
			let NA=typ[0]==='?';  if(isNul) return NA;
			return ONE(NA?typ.slice(1):typ, val);
		}
		const NA=typ[0]===true;  if(isNul) return NA;  const n=typ.length;
		for(let i=NA?1:0;i<n;i++) if(ONE(typ[i],val)) return true; return false;
	};
	return $.is(typ, val);
};
$._is_str = '#,hex,b64,email,url,abc,abcNum,abcDash,chs,chsAbc,chsAbcNum,chsDash,';
$._is = {
	int: /^-?\d+$/, // 整数
	num: /^-?\d+(\.\d+)?$/, // 数字(整数|小数)
	hex: /^[a-f\d]+$/i, // 16进制(不分大小写)  eg:md5
	b64: /^[a-z\d\/+]+={0,3}$/i, // BASE64  字母或数字或'+'或'/'或'='
	url: /^https?:\/\/([a-z\d_-]+\.)+[a-z\d]+(:\d+)?(\/.*)?$/i, // URL(http|https)
	abc: /^[A-Za-z]+$/, // 纯字母(不分大小写)
	abcNum: /^[A-Za-z\d]+$/, // 字母或数字
	abcDash: /^[A-Za-z\d\-_]+$/, // 字母或数字或下划线'_'或破折号'-'
	chs: /^[\u4e00-\u9fa5]+$/, // 汉字
	chsAbc: /^[\u4e00-\u9fa5a-zA-Z]+$/, // 汉字或字母
	chsAbcNum: /^[\u4e00-\u9fa5a-zA-Z\d]+$/, // 汉字或字母或数字
	chsDash: /^[\u4e00-\u9fa5a-zA-Z\d_\-]+$/, // 中英文,数字,下划线'_',破折号'-'
	email: /^[a-z\d_.-]+@([\da-z-]+\.)+[a-z]{2,6}$/i, // 邮箱地址
	mobile: /^1[3-9]\d{9}$/, // 手机号码(中国大陆)
	tel: /^0\d{2,3}-\d{7,8}$/, // 带区号的座机
	card: /^\d{17}(\d|x)$/i, //身份证号码(中国大陆)
	flg: /^[01]$/, // 是否标记:0|1
	big: 'int:[1,18446744073709551615]', // 数据库无符号BIGINT范围(除0)
	id: 'int:+[18,18]', // 服务器生成的ID  18位,2211-10-08前
	team: 'chsDash:[2,32]', // 组织/机构/企业/团队名
	name: 'chsDash:[2,8]', // 用户名/名称/名字等
	real: 'chs:[2,8]', // 真实姓名(中文)
};
